/*
 * Copyright (c) 2015 mgm technology partners GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mgmtp.jfunk.data.generator.constraint;

import static com.google.common.base.Preconditions.checkArgument;

import org.jdom.Element;

import com.mgmtp.jfunk.common.random.MathRandom;
import com.mgmtp.jfunk.data.generator.Generator;
import com.mgmtp.jfunk.data.generator.constraint.base.BaseConstraint;
import com.mgmtp.jfunk.data.generator.control.FieldCase;
import com.mgmtp.jfunk.data.generator.util.XMLTags;

/**
 * This constraint can be used to combine two constraints such that they do not produce equal
 * values. The target constraint does not generate values which match the value generated by the
 * source constraint.
 * <p>
 * Example:
 * 
 * <pre>
 * {@code
 * <constraint class="com.mgmtp.jfunk.data.generator.constraint.ExcludeConstraint">
 *   <source>
 *     <constraint>
 *       <field>
 *         <value>0</value>
 *       </field>
 *     </constraint>
 *   </source>
 *   <target>
 *     <constraint>
 *       <field_ref id="integer_number"/>
 *     </constraint>
 *   </target>
 * </constraint>
 * }
 * </pre>
 * 
 * Assuming the field {@code integer_number} represents all integer numbers the example would
 * generate all integer numbers except 0.<br>
 * After ten tries this constraint gives up and returns the last generated value - it does not fail
 * in this case!
 * 
 */
public class ExcludeConstraint extends BaseConstraint {

	private final Constraint source;
	private final Constraint target;

	public ExcludeConstraint(final MathRandom random, final Element element, final Generator generator) {
		super(random, element, generator);
		Element sourceElement = element.getChild(XMLTags.SOURCE);
		Element targetElement = element.getChild(XMLTags.TARGET);
		checkArgument(sourceElement != null, "ExcludeConstraint needs a source element");
		checkArgument(targetElement != null, "ExcludeConstraint needs a target element");

		source = getConstraint(sourceElement);
		target = getConstraint(targetElement);
	}

	@Override
	public int getMaxLength() {
		return target.getMaxLength();
	}

	@Override
	public String initValues(final FieldCase c) {
		if (c == FieldCase.NULL) {
			return null;
		}
		String excludeValue = source.initValues(c);
		String value = target.initValues(c);
		int counter = 10;
		while (excludeValue.equals(value) && counter > 0) {
			if (log.isDebugEnabled()) {
				log.debug("Value=" + excludeValue + " is not allowed (constraint id=" + getLastIdInHierarchy() + "); generating new value");
			}
			target.resetValues();
			value = target.initValues(c);
			counter--;
		}
		return value;
	}

	@Override
	protected String initValuesImpl(final FieldCase c) throws Exception {
		throw new UnsupportedOperationException("Must not be called for this combined constraint");
	}

	/**
	 * {@link #resetValues()} for the target constraint
	 */
	@Override
	public void resetValues() {
		target.resetValues();
	}

	/**
	 * @return {@link #countCases()} for the target constraint
	 */
	@Override
	public int countCases() {
		return target.countCases();
	}

	/**
	 * @return {@link #hasNextCase()} for the target constraint
	 */
	@Override
	public boolean hasNextCase() {
		return target.hasNextCase();
	}

	/**
	 * {@link #resetCase()} for the target constraint
	 */
	@Override
	public void resetCase() {
		target.resetCase();
	}
}